/*******************************************************************************
 * Copyright (c) 2011 Google, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Google, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.wb.core.controls;

import org.eclipse.wb.draw2d.IColorConstants;
import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TypedListener;
import org.eclipse.swt.widgets.Widget;

/**
 * Combo control for {@link PropertyTable} and combo property editors.
 *
 * @author scheglov_ke
 * @coverage core.control
 */
public class CCombo3 extends Composite {
  private final long m_createTime = System.currentTimeMillis();
  private final CImageLabel m_text;
  private final Button m_arrow;
  private final Shell m_popup;
  private final Table m_table;
  private boolean m_fullDropdownTableSize = false;

  ////////////////////////////////////////////////////////////////////////////
  //
  // Constructor
  //
  ////////////////////////////////////////////////////////////////////////////
  public CCombo3(Composite parent, int style) {
    super(parent, style);
    addEvents(this, m_comboListener, new int[]{SWT.Dispose, SWT.Move, SWT.Resize});
    // create label
    {
      m_text = new CImageLabel(this, SWT.NONE);
      new DefaultControlActionsManager(m_text);
      addEvents(m_text, m_textListener, new int[]{
          SWT.KeyDown,
          SWT.KeyUp,
          SWT.MouseDown,
          SWT.MouseUp,
          SWT.MouseMove,
          SWT.MouseDoubleClick,
          SWT.Traverse,
          SWT.FocusIn,
          SWT.FocusOut});
    }
    // create arrow
    {
      m_arrow = new Button(this, SWT.ARROW | SWT.DOWN);
      addEvents(m_arrow, m_arrowListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
    }
    // create popup Shell
    {
      Shell shell = getShell();
      m_popup = new Shell(shell, SWT.NONE);
      m_popup.setLayout(new FillLayout());
    }
    // create table for items
    {
      m_table = new Table(m_popup, SWT.FULL_SELECTION);
      addEvents(m_table, m_tableListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
      //
      new TableColumn(m_table, SWT.NONE);
    }
    // Focus tracking filter
    {
      final Listener filter = new Listener() {
        private boolean hasFocus;

        public void handleEvent(Event event) {
          boolean old_hasFocus = hasFocus;
          hasFocus =
              m_text.isFocusControl()
                  || m_arrow.isFocusControl()
                  || m_popup.isFocusControl()
                  || m_table.isFocusControl();
          // configure colors
          if (hasFocus) {
            m_text.setBackground(IColorConstants.listSelection);
            m_text.setForeground(IColorConstants.listSelectionText);
          } else {
            m_text.setBackground(IColorConstants.listBackground);
            m_text.setForeground(IColorConstants.listForeground);
          }
          // send FocusOut event
          if (old_hasFocus && !hasFocus) {
            Event e = new Event();
            e.widget = CCombo3.this;
            e.time = event.time;
            notifyListeners(SWT.FocusOut, e);
          }
        }
      };
      getDisplay().addFilter(SWT.FocusIn, filter);
      addListener(SWT.Dispose, new Listener() {
        public void handleEvent(Event event) {
          getDisplay().removeFilter(SWT.FocusIn, filter);
        }
      });
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Events handling
  //
  ////////////////////////////////////////////////////////////////////////////
  private final Listener m_comboListener = new Listener() {
    public void handleEvent(Event event) {
      switch (event.type) {
        case SWT.Dispose :
          if (!m_popup.isDisposed()) {
            m_popup.dispose();
          }
          break;
        case SWT.Move :
          doDropDown(false);
          break;
        case SWT.Resize :
          doResize();
          break;
      }
    }
  };
  private final Listener m_textListener = new Listener() {
    public void handleEvent(final Event event) {
      switch (event.type) {
        case SWT.MouseDown :
          if (System.currentTimeMillis() - m_createTime < 400) {
            // send "logical" double click for case when we just activated combo
            // and almost right away click second time (but first time on editor)
            event.detail = -1;
            notifyListeners(SWT.MouseDoubleClick, event);
            // when we use "auto drop on editor activation" option, this click is
            // is "logically" second one, so it should close combo
            if (!isDisposed()) {
              doDropDown(false);
            }
          } else {
            m_text.setCapture(true);
            doDropDown(!isDropped());
          }
          break;
        case SWT.MouseUp : {
          m_text.setCapture(false);
          TableItem item = getItemUnderCursor(event);
          if (item != null) {
            doDropDown(false);
            sendSelectionEvent(event);
          }
          break;
        }
        case SWT.MouseDoubleClick :
          // prevent resending MouseDoubleClick that we sent on fast MouseDown
          if (event.detail != -1) {
            notifyListeners(SWT.MouseDoubleClick, event);
          }
          break;
        case SWT.MouseMove : {
          TableItem item = getItemUnderCursor(event);
          if (item != null) {
            m_table.setSelection(new TableItem[]{item});
          }
          break;
        }
        case SWT.KeyDown : {
          // check for keyboard navigation and selection
          {
            int selectionIndex = m_table.getSelectionIndex();
            if (event.keyCode == SWT.ARROW_UP) {
              selectionIndex--;
              if (selectionIndex < 0) {
                selectionIndex = m_table.getItemCount() - 1;
              }
              m_table.setSelection(selectionIndex);
              return;
            } else if (event.keyCode == SWT.ARROW_DOWN) {
              m_table.setSelection((selectionIndex + 1) % m_table.getItemCount());
              return;
            } else if (event.character == SWT.CR || event.character == ' ') {
              sendSelectionEvent(event);
              return;
            }
          }
          // be default just resend event
          resendKeyEvent(event);
          break;
        }
        case SWT.KeyUp :
          resendKeyEvent(event);
          break;
      }
    }

    private TableItem getItemUnderCursor(Event event) {
      Point displayLocation = m_text.toDisplay(new Point(event.x, event.y));
      Point tableLocation = m_table.toControl(displayLocation);
      return m_table.getItem(tableLocation);
    }
  };
  private final Listener m_arrowListener = new Listener() {
    public void handleEvent(Event event) {
      switch (event.type) {
      /*case SWT.FocusIn : {
       resendFocusEvent(event);
       break;
       }*/
        case SWT.Selection : {
          doDropDown(!isDropped());
          break;
        }
      }
    }
  };
  private final Listener m_tableListener = new Listener() {
    public void handleEvent(Event event) {
      switch (event.type) {
        case SWT.Selection : {
          doDropDown(false);
          // show selected item in text
          {
            int index = m_table.getSelectionIndex();
            select(index);
          }
          // send selection event
          sendSelectionEvent(event);
          break;
        }
      }
    }
  };

  ////////////////////////////////////////////////////////////////////////////
  //
  // Events utils
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Sends selection event.
   */
  private void sendSelectionEvent(Event event) {
    Event e = new Event();
    e.time = event.time;
    e.stateMask = event.stateMask;
    notifyListeners(SWT.Selection, e);
  }

  /**
   * Resends KeyDown/KeyUp events.
   */
  private void resendKeyEvent(Event event) {
    Event e = new Event();
    e.time = event.time;
    e.character = event.character;
    e.keyCode = event.keyCode;
    e.stateMask = event.stateMask;
    notifyListeners(event.type, e);
  }

  /**
   * Adds given listener as handler for events in given widget.
   */
  private void addEvents(Widget widget, Listener listener, int[] events) {
    for (int i = 0; i < events.length; i++) {
      widget.addListener(events[i], listener);
    }
  }

  /**
   * Adds the listener to receive events.
   */
  public void addSelectionListener(SelectionListener listener) {
    checkWidget();
    if (listener == null) {
      SWT.error(SWT.ERROR_NULL_ARGUMENT);
    }
    TypedListener typedListener = new TypedListener(listener);
    addListener(SWT.Selection, typedListener);
    addListener(SWT.DefaultSelection, typedListener);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Activity
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Sets drop state of combo.
   */
  public void doDropDown(boolean drop) {
    // check, may be we already in this drop state
    if (drop == isDropped()) {
      return;
    }
    // close combo
    if (!drop) {
      m_popup.setVisible(false);
      m_text.setFocus();
      return;
    }
    // open combo
    {
      // prepare popup location
      Point comboSize = getSize();
      Point popupLocation;
      {
        //popupLocation = getParent().toDisplay(getLocation());
        popupLocation = toDisplay(new Point(0, 0));
        popupLocation.y += comboSize.y;
      }
      // calculate and set popup location
      {
        TableColumn tableColumn = m_table.getColumn(0);
        // pack everything
        tableColumn.pack();
        m_table.pack();
        m_popup.pack();
        // calculate bounds
        Rectangle tableBounds = m_table.getBounds();
        tableBounds.height = Math.min(tableBounds.height, m_table.getItemHeight() * 20); // max 20 items without scrolling
        m_table.setBounds(tableBounds);
        // calculate size
        int remainingDisplayHeight = getDisplay().getClientArea().height - popupLocation.y - 10;
        int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
        int remainingDisplayWidth = getDisplay().getClientArea().width - popupLocation.x - 5;
        int preferredWidth =
            isFullDropdownTableWidth()
                ? Math.min(tableBounds.width, remainingDisplayWidth)
                : comboSize.x;
        // set popup bounds calculated as computeTrim basing on combo width and table height paying attention on remaining display space
        Rectangle popupBounds =
            m_popup.computeTrim(popupLocation.x, popupLocation.y, preferredWidth, preferredHeight);
        m_popup.setBounds(popupBounds);
        // adjust column size
        tableColumn.setWidth(m_table.getClientArea().width);
      }
      m_popup.setVisible(true);
      // scroll to selection if needed
      m_table.showSelection();
    }
  }

  /**
   * Initiates "press-hold-drag" sequence.
   */
  public void startDrag() {
    m_text.setCapture(true);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Access
  //
  ////////////////////////////////////////////////////////////////////////////
  public void setFullDropdownTableWidth(boolean freeTableSize) {
    m_fullDropdownTableSize = freeTableSize;
  }

  public boolean isFullDropdownTableWidth() {
    return m_fullDropdownTableSize;
  }

  public boolean isDropped() {
    return m_popup.isVisible();
  }

  public void setQuickSearch(boolean value) {
    // TODO
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Access: items
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Removes all items.
   */
  public void removeAll() {
    TableItem[] items = m_table.getItems();
    for (int index = 0; index < items.length; index++) {
      TableItem item = items[index];
      item.dispose();
    }
  }

  /**
   * Adds new item with given text.
   */
  public void add(String text) {
    add(text, null);
  }

  /**
   * Adds new item with given text and image.
   */
  public void add(String text, Image image) {
    checkWidget();
    TableItem item = new TableItem(m_table, SWT.NONE);
    item.setText(text);
    item.setImage(image);
  }

  /**
   * @return an item at given index
   */
  public String getItem(int index) {
    checkWidget();
    return m_table.getItem(index).getText();
  }

  /**
   * @return the number of items
   */
  public int getItemCount() {
    checkWidget();
    return m_table.getItemCount();
  }

  /**
   * @return the index of the selected item
   */
  public int getSelectionIndex() {
    checkWidget();
    return m_table.getSelectionIndex();
  }

  /**
   * Selects an item with given index.
   */
  public void select(int index) {
    checkWidget();
    if (index == -1) {
      m_table.deselectAll();
      m_text.setText(null);
      m_text.setImage(null);
      return;
    } else {
      TableItem item = m_table.getItem(index);
      m_text.setText(item.getText());
      m_text.setImage(item.getImage());
      m_table.select(index);
    }
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Access: text and image
  //
  ////////////////////////////////////////////////////////////////////////////
  /**
   * Selects item with given text.
   */
  public void setText(String text) {
    // try to find item with given text
    TableItem[] items = m_table.getItems();
    for (int index = 0; index < items.length; index++) {
      TableItem item = items[index];
      if (item.getText().equals(text)) {
        select(index);
        return;
      }
    }
    // not found, remove selection
    select(-1);
  }

  ////////////////////////////////////////////////////////////////////////////
  //
  // Resize support
  // TODO: computeSize
  //
  ////////////////////////////////////////////////////////////////////////////
  protected void doResize() {
    Rectangle clientArea = getClientArea();
    int areaWidth = clientArea.width;
    int areaHeight = clientArea.height;
    // compute sizes of controls
    Point buttonSize = m_arrow.computeSize(areaHeight, areaHeight);
    Point textSize = m_text.computeSize(areaWidth - buttonSize.x, areaHeight);
    // set controls location/size
    m_arrow.setLocation(areaWidth - buttonSize.x, 0);
    m_arrow.setSize(buttonSize);
    m_text.setSize(areaWidth - buttonSize.x, Math.max(textSize.y, areaHeight));
  }
}
